using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Fated.Character;
using Fated.Character.Data;
using Fated.BattleEngine.Data;
using Fated.SaveEngine;
using Fated;

namespace Fated.BattleEngine {
    public class TurnData {
        public int turnValue = 0;
        public bool aiControlled = true;
        public bool friendly = false;
        public BattleNPC npcData = null;
        public bool isDefending = false;
    }
    public static class BattleInterface {

        // For testing only! At least... I think?
        private static SpriteFont font;

        private static Tree<string> currentMenu;

        private enum MenuState {
            Free, Menu,
            Target, Animating, Dialogue,
            NextTurn, Finished
        };

        private static MenuState currentState;

        private const int ACTIONSPERTURN = 2;
        private const float CAMERAMOVESPEED = 1.0f;
        private const int MAXTURNVALUE = 100;
        private const string BATTLEFOLDER = "battles\\";
        private static ContentManager content;
        private static Vector2 screenSize;

        // This one is special and we need to keep it.
        private static TurnData playerData;
        private static TurnData currentTurn;

        private static BattleData currentBattle;
        private static BattleMap battleMap;
        private static string currentAction;

        private static int actionCount = 0;

        private static List<TurnData> units = new List<TurnData>();

        private enum Control { CursorStick, CameraStick };
        private static long[] controlIndices = new long[2];

        public static void initialize(ContentManager cM, int screenWidth, int screenHeight) {
            font = cM.Load<SpriteFont>("fonts\\defaultFont");
            currentState = MenuState.Finished;
            screenSize = new Vector2(screenWidth, screenHeight);
            content = cM;
            for (int i = 0; i < controlIndices.Length; i++) {
                controlIndices[i] = TimingMngr.registerControl();
            }
        }

        public static void startBattle(string battleName) {
            actionCount = 0;
            units.Clear();
            currentBattle = content.Load<BattleData>(BATTLEFOLDER + battleName);

            battleMap = content.Load<BattleMap>(BATTLEFOLDER + currentBattle.map);

            foreach (NPCData n in currentBattle.allies) {
                TurnData d = new TurnData();
                d.friendly = true;
                d.aiControlled = true;
                d.npcData = new BattleNPC(n);
                units.Add(d);
            }
            foreach (NPCData n in currentBattle.enemies) {
                TurnData d = new TurnData();
                d.friendly = false;
                d.aiControlled = true;
                d.npcData = new BattleNPC(n);
                units.Add(d);
            }

            playerData = new TurnData();
            playerData.friendly = true;
            playerData.aiControlled = false;
            playerData.npcData = new BattleNPC(CurrentCharacter.Data);
            units.Add(playerData);

            CurrentCharacter.AllyIterator.reset();
            while (CurrentCharacter.AllyIterator.hasNext()) {
                TurnData d = new TurnData();
                d.friendly = true;
                d.aiControlled = false;
                d.npcData = new BattleNPC(CurrentCharacter.AllyIterator.getNextAllyData());
                units.Add(d);
            }

            battleMap.setup(content, units);
            for (int i = 0; i < units.Count(); i++){
                units[i].npcData.Z = battleMap.getTileHeight((int)units[i].npcData.X, (int)units[i].npcData.Y);
            }

            nextTurn();
        }

        public static void stopBattle() {
            CurrentCharacter.Data = playerData.npcData.ToJSON();
            List<Dictionary<string, object>> AllyData = new List<Dictionary<string,object>>();
            foreach (TurnData d in units){
                if (d.friendly && !d.aiControlled){
                    AllyData.Add(d.npcData.ToJSON());
                }
            }
            CurrentCharacter.mergeAllyData(AllyData);
        }

        /// <summary>
        /// Updates inputs and animations.
        /// </summary>
        /// <param name="timeElapsed">Time elapsed in seconds since last frame.</param>
        /// <param name="currentPad">GamePadState for this frame</param>
        public static void update(double timeElapsed, InputState pad) {
            if (Finished) return;
            if (currentState == MenuState.NextTurn) {
                nextTurn();
                return;
            } else if (currentState == MenuState.Menu) {

                if (!MenuRenderer.Started) {
                    displayMenu();

                } else if (MenuRenderer.Finished) {
                    if ((currentAction = MenuRenderer.getResult()) == null) {
                        currentState = MenuState.Free;
                    } else {
                        currentState = MenuState.Target;
                        if (currentAction == "Move") {
                            battleMap.isRangeVisible(true);
                            battleMap.setRangeRadius(currentTurn.npcData.MoveRange);
                            battleMap.setRangeCenter(currentTurn.npcData.GridPosition);
                        }
                    }
                }

            } else if (currentState == MenuState.Animating) {
                // Animate until done, then decide if
                // next turn or menu again
                if (actionCount <= 0) {
                    currentState = MenuState.NextTurn;
                } else {
                    currentState = MenuState.Menu;
                }
            }
            //if (checkCriteria()) {Finished = true};
            updateControls(timeElapsed, pad);
            updateAnimations(timeElapsed);

            // Save old state for next frame
        }
        private static void updateControls(double timeElapsed, InputState pad) {

            if (currentTurn.aiControlled == true) {
                //doAI();
                battleMap.setCursor(new Point((int)currentTurn.npcData.X - 1, (int)currentTurn.npcData.Y));
                battleMap.moveCharacter(currentTurn.npcData.GridPosition);
                nextTurn();
            } else {
                MenuRenderer.update(pad, timeElapsed);
                if (TimingMngr.tickAvailable(pad.PrimaryStick, controlIndices[(int)Control.CursorStick], timeElapsed)) {
                    moveCursor(pad.PrimaryStick);
                }
                if (pad.SecondaryStick.Length() > 0.001) {
                    moveCamera(pad.SecondaryStick);
                }

                if (pad.UniqueSelectButtonPress) {
                    if (currentState == MenuState.Free) {
                        displayMenu();
                    } else if (currentState == MenuState.Target) {
                        performAction();
                    } else if (currentState == MenuState.Dialogue) {
                        // advanceDialogue
                        currentState = MenuState.NextTurn;
                    }
                }
            }
        }
        private static void performAction() {
            if (currentAction == "Move") {
                // Move to AbstractCharacter
                int range = currentTurn.npcData.MoveRange;
                Point start = currentTurn.npcData.GridPosition;
                int dist = battleMap.distance(start);
                if (range >= dist && !battleMap.containsCharacter()) {
                    // Then actually move
                    battleMap.moveCharacter(start);
                    actionCount -= 1;
                    currentState = MenuState.Animating;
                } else {
                    // Fail
                }
            } else if (currentAction == "Defend") {
                // Can always defend.
                currentTurn.isDefending = true;
                currentState = MenuState.Animating;

            } else if (currentAction == "Attack") {
                int range = currentTurn.npcData.AttackRange;
                Point start = currentTurn.npcData.GridPosition;
                int dist = battleMap.distance(start);
                if (range >= dist && !battleMap.containsCharacter()) {
                    currentState = MenuState.Animating;
                }
            } else {
                Ability action = currentTurn.npcData.getAbility(currentAction);
                int range = action.Range;
                Point start = currentTurn.npcData.GridPosition;
                int dist = battleMap.distance(start);
                if (range >= dist && !battleMap.containsCharacter()) {
                    currentState = MenuState.Animating;
                }
            }
            // If we aren't targeting, then range doesn't make sense.
            if (currentState != MenuState.Target) {
                battleMap.isRangeVisible(false);
            }
        }
        private static void displayMenu() {
            currentMenu = new Tree<string>();
            currentMenu.Add("Move");
            currentMenu.Add("Attack");
            
            List<string> abilNames = currentTurn.npcData.Abilities;
            if (abilNames.Count() > 0) {
                Tree<string> abilities = new Tree<string>("Abilities");
                foreach (string s in abilNames) {
                    abilities.Add(s);
                }
                currentMenu.Add(abilities);
            }
            currentMenu.Add("Defend");
            MenuRenderer.showMenu(currentMenu);
            currentState = MenuState.Menu;
            battleMap.setCursor(new Point((int)currentTurn.npcData.X, (int)currentTurn.npcData.Y));
        }

        private static void moveCamera(Vector2 change) {
            change *= CAMERAMOVESPEED;
            throw new NotImplementedException();
        }

        private static void moveCursor(Vector2 change) {
            if (currentState == MenuState.Free || currentState == MenuState.Target) {
                battleMap.moveCursorBy((int)Math.Round(change.X),
                    -(int)Math.Round(change.Y));
                //updateHUD();
            }
        }
        private static void updateAnimations(double timeElapsed) {

        }
        
        public static void render(SpriteBatch sB) {
            if (Finished) return;

            //Again for testing
            sB.DrawString(font, currentState.ToString(), new Vector2(0, 250), Color.White);

            MapDisplayer.renderMap(battleMap);
            if (!MenuRenderer.Finished) {
                sB.Draw(MenuRenderer.getTexture(),
                    new Rectangle(0, 0, MenuRenderer.Width, MenuRenderer.Height),
                    null, Color.White, 0f, Vector2.Zero, SpriteEffects.None, .1f);
            }
        }

        private static void nextTurn() {
            int nextTurn = 0;

            // Since Speed >= 1, interval cannot be above 100. 1000 will do.
            double interval = 1000;
            for(int i = 0 ; i < units.Count(); i++){
                TurnData currData = units[i];
                // How much farther to 100 for this character?
                int timeLeft = MAXTURNVALUE - currData.turnValue;
                double stepsLeft = (double)timeLeft / (double)currData.npcData.Speed;
                if (stepsLeft < interval) {
                    nextTurn = i;
                    interval = stepsLeft;
                }
            }
            // Now we know the next turn, and how long it takes to get there,
            // So scale each timer to match, then give the next turn.
            for (int i = 0; i < units.Count(); i++) {
                TurnData currData = units[i];
                currData.turnValue = (int)(currData.npcData.Speed * interval);
            }
            // Yay reference types!
            currentTurn = units[nextTurn];

            // reset character state
            currentTurn.isDefending = false;
            actionCount = ACTIONSPERTURN;
            currentState = MenuState.Menu;
            currentTurn.turnValue = 0;
        }

        public static Dictionary<string, object> Data {
            get;
            set;
        }

        public static bool Finished { get { return currentState == MenuState.Finished; } }
    }
}
